// 核心入口-MyVue类的实现
class MyVue {
    constructor(option) {
        //  option 是new Vue({ el...data...})
        // 类中有一个关键字指向实例对象 -> this
        this.$data = option.data;
        this.$el = document.querySelector(option.el)
        this.$methods = option.methods;

        // 1，数据代理
        this.proxyData()

        // 2，数据劫持  
        new Observer(this.$data)

        // 3，模板编译，this指向vue实例
        new Compile(this)

    }

    // 数据代理
    proxyData() {
        for (const key in this.$data) {
            Object.defineProperty(this, key, {
                // 是否可以修改
                configurable: false,
                // 是否可以枚举
                enumerable: true,
                get() {
                    return this.$data[key]
                },
                set(newVal) {
                    this.$data[key] = newVal
                }
            })
        }
    }
}

// 模板编译-Compile类的实现
class Compile {
    // vm就是vue实例
    constructor(vm) {
        // 模板容器
        this.el = vm.$el;
        this.vm = vm;

        // 编译模板：本质就是检查模板容器的每一个子节点，当遇到我们做的特殊标记(双花括号，指令等)时，对其进行解析。
        // 思路：遍历容器中所有的子节点，childNodes，但是频繁的操作DOM元素会造成严重的性能问题。
        // 通过文档片段来编译模板
        // 1，createDocumentFragment()方法，是用来创建一个虚拟的文档碎片节点对象
        // 2，DocumentFragment节点不属于文档树，它有如下特点：
        //  2-1 当把该节点插入文档树时，插入的不是该节点自身，而是它所有的子孙节点
        //  2-2 当添加多个dom元素时，如果先将这些元素添加到 DF中，再统一将 DF添加到页面，会减少
        //       页面的重排和重绘，进而提升页面渲染性能。
        //  2-3 使用 appendChild 方法将 dom树中的节点添加到 DF 中时，会删除原来的节点

        // 1，将模板容器中的所有子节点添加到文档片段中
        const fragment = this.nodeToFragment()

        // 2，编译文档片段（解析{{}}，指令）
        this.compile(fragment)

        // 3，将编译后的文档片段追加到模板容器中
        this.el.appendChild(fragment)
    }

    nodeToFragment() {
        // 创建文档片段
        const f = document.createDocumentFragment()

        // 将模板容器中的所有子节点添加到文档片段中
        // 当this.el.firstChild为null 也就是说它的布尔值为false时跳出while循环
        while (this.el.firstChild) {
            f.appendChild(this.el.firstChild)
        }
        // this.el.childNodes获取this.el 容器的所有子节点
        // console.log(this.el.childNodes, this.el.firstChild)

        return f
    }

    // 编译文档片段
    compile(fragment) {
        const nList = fragment.childNodes
        // console.log(nList) =>  MyVue.js:NodeList(5) [text, input, text, div, text]

        nList.forEach(node => {
            const nType = node.nodeType
            if (nType === 1) {
                // 元素节点，解析指令
                this.compileElement(node)
            } else if (nType === 3) {
                // 文档节点，解析{{}}
                this.compileText(node)
            }

            // 如果node有子节点，则递归调用compile
            if (node.childNodes && node.childNodes.length > 0) {
                this.compile(node)
            }
        })
    }

    // 解析文档节点
    compileText(node) {
        const con = node.textContent
        const reg = /\{\{(.+?)\}\}/g
        // console.log(con)

        if (reg.test(con)) {
            // console.log(con)
            // ...args es6 剩余参数的写法[]
            // con -> {{msg}} --- {{info}} -> Hell0 --- World -> 123 --- World
            // 
            // compileUtil.text(node, con, this.vm)
            const newVal = con.replace(reg, (...args) => {
                // key -> args[1]
                // obj -> this.vm.$data
                // console.log(args)
                new Watcher(this.vm, args[1], () => {
                    node.textContent = con.replace(reg, (...args) => {
                        return this.vm.$data[args[1]]
                    })
                })
                return this.vm.$data[args[1]]
            })

            // console.log(newVal)
            node.textContent = newVal
        }
    }

    // 解析元素节点
    compileElement(node) {
        // 1,获取元素节点的所有属性   atrs是一个伪数组
        const atrs = node.attributes
        // console.log(atrs, Array.from(atrs))
        // 将伪数组atrs转化为真数组,才能使用forEach方法
        Array.from(atrs).forEach(atr => {
            // console.log(atr)  输出type="text"   v-model="msg"
            // console.dir(atr)  输出type   v-model  主要关注他们的name和value
            // 用 ES6解构atr  
            const { name, value } = atr
            // console.log(name, value)输出  type text   
            //                              v-model msg
            if (name.startsWith('v-')) {
                // 用es6语法将model解构出来
                const [, b] = name.split('-') // ['v', 'model']
                // console.log(b)
                //将 model进行编译
                if (b === 'model') {
                    // key -> value
                    // obj -> this.vm.$data
                    // obj[key] -> this.vm.$data[value]
                    node.value = this.vm.$data[value]

                    // 数据驱动视图
                    new Watcher(this.vm, value, () => {
                        node.value = this.vm.$data[value]
                    })

                    node.addEventListener('input', (e) => {
                        // console.log(e.target.value)
                        this.vm.$data[value] = e.target.value
                    })
                }

                if (b === 'html') {
                    // console.log(this.vm.$data[value])
                    node.innerHTML = this.vm.$data[value]
                }

                if (b === 'click') {
                    const fn = this.vm.$methods[value]
                    // console.log(value, fn)
                    node.addEventListener('click', fn.bind(this.vm))
                }

                if (b === 'show') {

                }
            }
        })
    }


}

// 数据劫持
class Observer {
    constructor(data) {
        this.observer(data)
    }
    // 数据劫持
    observer(data) {
        const dep = new Dep();
        for (const key in data) {
            let val = data[key]
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: false,
                get() {
                    // if (Dep.target) dep.addSub(Dep.target);
                    Dep.target && dep.addSub(Dep.target);
                    return val
                },
                set(newVal) {
                    // 对数据变化进行监听
                    val = newVal
                    dep.notify()
                }
            })
        }
    }
}

// Wacth观察者
class Watcher {
    // 当观察者观察的数据（msg, info）变化时，就去更新视图
    constructor(vm, key, cb) {
        this.vm = vm;
        this.key = key;
        this.cb = cb;

        // 当观察者实例化时，将最初的值保存
        this.oldVla = this.getOldVal()
    }

    getOldVal() {
        Dep.target = this;

        // 本质是对 mv 实例对象中的 mv.$data[key]
        const oldVal = this.getValue(this.key, this.vm)
        Dep.target = null;

        return oldVal
    }

    getValue(key, vm) {
        //  数据单一情况 return vm.$data[key]
        //  以下是数据复杂，多层嵌套情况：
        key = key.replace(/\s+/g, ''); // 去掉空格
        console.log('key', key)
        return key.split('.').reduce((data, currentVal) => {
            // console.log(1, currentVal, data[currentVal]);
            return data[currentVal];
        }, vm.$data);
    }

    // 更新视图
    update() {
        this.cb()
    }
}

// 发布订阅 - Dep 类的实现
class Dep {
    // 当数据变化时，通知所有的观察者更新视图
    constructor() {
        this.sublist = []
    }

    // 添加观察者
    addSub(watcher) {
        this.sublist.push(watcher)
    }

    // 通知所有的观察者更新视图
    notify() {
        this.sublist.forEach(w => w.update())
    }
}